#include "Matrix.h"
#include "RangeException.h"
#include <iostream>
#include <ctime>
#include <cstdlib>
#include <cstddef>
#include <iomanip>
#include "MatrixParser.h"

/////////////////////////////////////////////////////////////////////////////////
//	 							CONSTRUCTORS								   //
/////////////////////////////////////////////////////////////////////////////////
//
// Matrix basic constructor involves matrix generation and intitialization
template <typename T>
Matrix<T>::Matrix(unsigned  r, unsigned  c) : rows(r), cols(c)
{
	input = NULL;
	memoryAllocated = false;
	exceptionOccured = false;
	if(rows == 0 || cols == 0)
	{
		throw RangeException("Range exception thrown. Matrix constructor has zero size.");
	}
	elements = rows * cols;
	data = new T * [rows];
	for(unsigned i = 0; i < rows; i++)
	{
		data[i] = new T[cols];
	}
	memoryAllocated = true;
	for(unsigned i = 0; i < rows; i++)
	{
		for(unsigned j = 0; j < cols; j++)
		{
			data[i][j] = static_cast<T>(0.0);
		}
	}
	isEmpty = true;
}

// Matrix constructor for direct user input
template <typename T>
Matrix<T>::Matrix(const char * str) : input(str)
{
	memoryAllocated = false;
	exceptionOccured = false;
	isEmpty = true;
	rows = 0;
	cols = 0;
	elements = 0;
	MatrixParser<T>::initialize(*this, input);
}


// Basic constructor
template <typename T>
Matrix<T>::Matrix()
{
	cols = rows = 0;
	elements = 0;
	input = NULL;
	data = NULL;
	isEmpty = true;
	exceptionOccured = false;
	memoryAllocated = false;
}

// Copy constructor
template <typename T>
Matrix<T>::Matrix(const Matrix& m)
{
	//std::cout << "Copy constructor has been called." << std::endl;
	input = NULL;
	isEmpty = true;
	exceptionOccured = false;
	if(m.memoryAllocated)
	{
		rows = m.getRows();
		cols = m.getCols();
		elements = rows * cols;
		if(elements)
		{
			data = new double * [rows];
			for(unsigned i = 0; i < rows; i++)
				data[i] = new double[cols];
			memoryAllocated = true;
			for(unsigned i = 0; i < rows; i++)
			{
				for(unsigned j = 0; j < cols; j++)
				{
					data[i][j] = m.data[i][j];
				}
			}
		}
	}
	else
		data = NULL;
}


/////////////////////////////////////////////////////////////////////////////////
////	 							DESTRUCTORS									 //
/////////////////////////////////////////////////////////////////////////////////

// this method destroys the matrix with its corresponding rows
template <typename T>
Matrix<T>::~Matrix()
{
	if(memoryAllocated)
	{
		for(unsigned i = 0; i < rows; i++)
		{
			delete[] data[i];
		}
		delete[] data;
	}
}

/////////////////////////////////////////////////////////////////////////////////
////	 							OPERATORS 									 //
/////////////////////////////////////////////////////////////////////////////////

template <typename T>
T Matrix<T>::operator() (unsigned r, unsigned c) const
{
	//std::cout << "Matrix::operator() const" << std::endl;
	try
	{
		if(memoryAllocated)
		{
			if((r > 0 && r <= rows) && (c > 0 && c <= cols))
				return data[r - 1][c - 1];
		}
		throw RangeException("RangeException thrown. Invalid range.");
	}
	catch(RangeException & e)
	{
		std::cout << e.what() << std::endl;
	}
}

template <typename T>
T& Matrix<T>::operator() (unsigned r, unsigned c)
{
	//std::cout << "Matrix::operator() non-const" << std::endl;
	if(memoryAllocated && (r > 0 && r <= rows) && (c > 0 && c <= cols))
	{
		return data[r - 1][c - 1];
	}
	else
	{
		throw RangeException("Range exception thrown. Invalid range.");
	}
}



template <typename T1>
std::ostream& operator<< (std::ostream & os, const Matrix<T1> & m)
{
	//std::cout << "Matrix::operator<<" << std::endl;
	os << "Ans = " << std::endl;
	for(unsigned i = 1; i <= m.getRows(); i++)
	{
		os.width(10);
		for(unsigned j = 1; j <= m.getCols(); j++)
		{
			os << m(i, j) << " ";
		}
		os << std::endl;
	}
	return os;
}

// Some operators return by value, some by reference. In general, an operator whose result is a new value
// (such as +, -, etc) must return the new value by value, and an operator whose result is an existing value,
// but modified (such as <<, >>, +=, -=, etc), should return a reference to the modified value.
template <typename T>
Matrix<T> & Matrix<T>::operator= (const Matrix<T> & m)
{
	if (this == &m) return *this;
	//std::cout << "Matrix::operator=" << std::endl;
	rows = m.getRows();
	cols = m.getCols();
	elements = rows * cols;

	data = new double * [rows];
	for(unsigned i = 0; i < rows; i++)
		data[i] = new double[cols];
	memoryAllocated = true;
	for(unsigned i = 0; i < rows; i++)
	{
		for(unsigned j = 0; j < cols; j++)
		{
			data[i][j] = m.data[i][j];
		}
	}
	return *this;
}

template <typename T>
Matrix<T> Matrix<T>::operator+ (const Matrix<T> & m)
{
	Matrix temp(*this);
	try
	{
		if(m.elements != elements)
			throw RangeException("RangeException. Number of elements is invalid.");

		for(unsigned i = 0; i < temp.rows; i++)
		{
			for(unsigned j = 0; j < temp.cols; j++)
			{
				temp.data[i][j] += m.data[i][j];
			}
		}
	}
	catch(RangeException & e)
	{
		std::cout << e.what() << std::endl;
	}
	return temp;
}



// friend operators (for other types)
template <typename T>
Matrix<T> operator+ (const Matrix<T> & m, const T & t)
{
	Matrix<T> temp(m);
	try
	{
		if(m.elements != 0)
			throw RangeException("RangeException. Number of elements is invalid.");

		for(unsigned i = 0; i < m.rows; i++)
		{
			for(unsigned j = 0; j < m.cols; j++)
			{
				m.data[i][j] += t;
			}
		}
	}
	catch(RangeException & e)
	{
		std::cout << e.what() << std::endl;
	}
	return temp;
}

template <typename T>
Matrix<T> operator- (const Matrix<T> & m, const Matrix<T> & n)
{
	Matrix<T> temp(m);
	try
	{
		if(m.elements != n.elements)
			throw RangeException("RangeException. Number of elements is invalid.");

		for(unsigned i = 0; i < temp.rows; i++)
		{
			for(unsigned j = 0; j < temp.cols; j++)
			{
				temp.data[i][j] -= m.data[i][j];
			}
		}
	}
	catch(RangeException & e)
	{
		std::cout << e.what() << std::endl;
	}
	return temp;
}


/////////////////////////////////////////////////////////////////////////////////
////	 							CUSTOM METHODS								 //
/////////////////////////////////////////////////////////////////////////////////


// this method generates random values  for matrix*/
template <typename T>
void Matrix<T>::random()
{
	// cast from time_t type
	srand(static_cast<unsigned>(time(NULL)));
	for(unsigned i = 0; i < rows; i++)
	{
		for(unsigned j = 0; j < cols; j++)
		{
			data[i][j] = rand()%10;
		}
	}
	isEmpty = false;
}

template <typename T>
void Matrix<T>::clear()
{
	for(unsigned i = 0; i < elements; i++) this->data[i] = 0;
	isEmpty = true;
}

template <typename T>
const unsigned Matrix<T>::getRows() const
{
	if(memoryAllocated) return rows;
	throw RangeException("RangeException caught. Memory not allocated.");
}

template <typename T>
const unsigned Matrix<T>::getCols() const
{
	if(memoryAllocated) return cols;
	throw RangeException("RangeException caught. Memory not allocated.");
}

